// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.asm.analysis;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.Value;

import com.dustedpixels.common.Objects;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class LocalsUsageInterpreter extends InterpreterAdapter {
  public LocalsUsageInterpreter() {
    super(new BasicInterpreter());
  }
  
  @Override
  public Value copyOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
    LocalsUsageValue unusedLocalsValue = (LocalsUsageValue) value;
    
    switch (insn.getOpcode()) {
      case Opcodes.ALOAD:
      case Opcodes.ILOAD:
      case Opcodes.LLOAD:
      case Opcodes.FLOAD:
      case Opcodes.DLOAD:
      case Opcodes.IINC:
        unusedLocalsValue.use();
        break;
    }    
    return unusedLocalsValue;
  }

  @Override
  public Value merge(Value value1, Value value2) {
    if (value1 == value2) {
      return value1;
    } else {
      LocalsUsageValue unusedLocalsValue1 = (LocalsUsageValue) value1;
      LocalsUsageValue unusedLocalsValue2 = (LocalsUsageValue) value2;
      
      if (unusedLocalsValue1.isUsed() && unusedLocalsValue2.isUsed()) {
        // If both merged values are already used, we may simply return one of them.
        return value1;
      } else if (value1.equals(value2)){
        return value1;
      } else {
        // Otherwise, we need return merged value.
        return LocalsUsageValue.merge(
            unusedLocalsValue1, 
            unusedLocalsValue2);
      }
    }
  }

  @Override
  protected Value wrapValue(Value value) {
    return LocalsUsageValue.newLeaf(value.getSize());
  }
  
  public static final class LocalsUsageValue implements Value {
    private final int size;
    private final boolean leaf;
    private final LocalsUsageValue sourceValue1;
    private final LocalsUsageValue sourceValue2;
    private boolean used;
    
    private static LocalsUsageValue newLeaf(int size) {
      return new LocalsUsageValue(size, true, null, null);
    }
    
    private static LocalsUsageValue merge(LocalsUsageValue value1, LocalsUsageValue value2) {
      return new LocalsUsageValue(value1.getSize(), false, value1, value2);
    }
    
    private LocalsUsageValue(
        int size,
        boolean leaf,
        LocalsUsageValue sourceValue1,
        LocalsUsageValue sourceValue2) {
      this.size = size;
      this.leaf = true;
      this.used = false;
      this.sourceValue1 = sourceValue1;
      this.sourceValue2 = sourceValue2;
    }

    public int getSize() {
      return size;
    }
    
    public boolean isUsed() {
      if (leaf) {
        return used;
      } else {
        return sourceValue1.isUsed() || sourceValue2.isUsed();
      }
    }

    private void use() {
      if (leaf) {
        used = true;
      } else {
        sourceValue1.use();
        sourceValue2.use();
      }
    }

    @Override
    public boolean equals(Object other) {
      if (this == other) return true;
      if (!(other instanceof LocalsUsageValue)) return false;
      LocalsUsageValue otherValue = (LocalsUsageValue) other;
      return Objects.eq(sourceValue1, otherValue.sourceValue1) &&
             Objects.eq(sourceValue2, otherValue.sourceValue2);
    }
  }  
}
